//
//  Copyright (C) 2010 Alexey Bobkov
//
//  This file is part of Fb2toepub converter.
//
//  Fb2toepub converter is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  Fb2toepub converter is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with Fb2toepub converter.  If not, see <http://www.gnu.org/licenses/>.
//


#include "hdr.h"

#include "base64.h"
#include "error.h"

typedef unsigned int BufType;

namespace Fb2ToEpub
{

    bool FB2TOEPUB_DECL DecodeBase64(const char *data, OutStmI *pout)
    {
        // table[' '] = table['\t] = table['\r'] = table['\n']  = 0xfd (whitespaces)
        // table['=']                                           = 0xfe (end of encoded stream)
        // table[<any symbol used for base64 encosing>]         = <6-bit value>
        // table[<all others>]                                  = 0xff (error or end)
        static const unsigned char table[256] =
        {
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xfd, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
            0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
            0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
            0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
        };

        const unsigned char *udata = reinterpret_cast<const unsigned char*>(data);
        char buf[256+4], *p = buf, *buf_end = buf + sizeof(buf) - 4;
        for(;;)
        {
            if(!*udata)
            {
                if(p > buf)
                    pout->Write(buf, p - buf);
                return true;
            }
#if defined (_DEBUG)
            unsigned char c = *udata++;
            BufType t = table[c];
#else
            BufType t = table[*udata++];
#endif
            if(t >= 0xfd)
            {
                if(t == 0xfd)           // whitespace
                    continue;
                if(p > buf)
                    pout->Write(buf, p - buf);
                return true;
            }
            BufType obuf = (t << 18);

            while((t = table[*udata++]) >= 0xfd)
                if(t != 0xfd)
                    return false;

            *p++ = (static_cast<char>((obuf += (t << 12)) >> 16));
#if defined (_DEBUG)
            while((t = table[c = *udata++]) >= 0xfd)
#else
            while((t = table[*udata++]) >= 0xfd)
#endif
                switch(t)
                {
                default:    return false;
                case 0xfd:  continue;   // whitespace
                case 0xfe:              // '='
                    if(p > buf)
                        pout->Write(buf, p - buf);
                    return true;
                }

            *p++ = (static_cast<char>((obuf += (t << 6)) >> 8));
#if defined (_DEBUG)
            while((t = table[c = *udata++]) >= 0xfd)
#else
            while((t = table[*udata++]) >= 0xfd)
#endif
                switch(t)
                {
                default:    return false;
                case 0xfd:  continue;   // whitespace
                case 0xfe:              // '='
                    if(p > buf)
                        pout->Write(buf, p - buf);
                    return true;
                }

            *p++ = (static_cast<char>(obuf + t));

            if(p > buf_end)
            {
                pout->Write(buf, p - buf);
                p = buf;
            }
        }
    }

};  //namespace Fb2ToEpub
